/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System System Specific Module                         */
/*                                                                           */
/*    Copyright (C) 2010 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    netIP - Network IP packet procesing functions                          */
/*****************************************************************************/

/*****************************************************************************/
/* Includes                                                                  */
/*****************************************************************************/
#include <krnlTypes.h>
#include <netPacketBuffer.h>
#include <netIP.h>
#include <netICMP.h>
#include <netUDP.h>
#include <netDNS.h>
#include "sysSettings.h"

/*****************************************************************************/
/* Constants                                                                 */
/*****************************************************************************/

// IP Header Field Positions
#define IP_HFP_VERSION_AND_LENGTH 0
#define IP_HPF_TYPE_OF_SERVICE		1
#define IP_HPF_LENGTH             2
#define IP_HPF_IDENTIFIER					4
#define IP_HFP_FRAGMENTATION      6
#define IP_HPF_TTL								8
#define IP_HFP_PROTOCOL           9
#define IP_HPF_HEADER_CHECKSUM		10
#define IP_HFP_SOURCE_IP          12
#define IP_HFP_DESTINATION_IP     16

/*****************************************************************************/
/* Compilation time checking                                                 */
/*****************************************************************************/
#if netIP_HEADER_LENGTH > netCHUNK_SIZE
#error Chunk size must be bigger than IP header size
#endif

/*****************************************************************************/
/* Module global variables                                                   */
/*****************************************************************************/
static dosWord l_ip_id = 0xf0; // Packet identifier

/*****************************************************************************/
/* Global variables                                                          */
/*****************************************************************************/
IN_ADDR g_netIPAddress;

/*****************************************************************************/
/* Private Functions																												 */
/*****************************************************************************/


/*****************************************************************************/
/* Public Functions                                                          */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
// Initialize IP module
void netIPInitialize(void)
{
  g_netIPAddress.Val = netDEFAULT_IP_ADDRESS;
#ifdef netDNS_ENABLED
  g_netPrimaryDNSAddress.Val = netDEFAULT_DNS_ADDRESS;
#endif
}

///////////////////////////////////////////////////////////////////////////////
//! Creates IP header
//! \param Parameters for header creation
void netIPCreateHeader( netIPHeaderInfo* in_header_info )
{
	dosByte* packet_buffer;
  dosDWord word_sum;
  dosWord checksum;
  
  // go to the buffer start
  netSetTXPacketPos( IP_HFP_VERSION_AND_LENGTH );
  packet_buffer = netGetTXPacketChunkBuffer();

	// set version and header length
	packet_buffer[IP_HFP_VERSION_AND_LENGTH]	= (4 << 4) + (in_header_info->HeaderLength / 4);
	packet_buffer[IP_HPF_TYPE_OF_SERVICE]			= 0;
	packet_buffer[IP_HPF_LENGTH]							= HIGH(in_header_info->PacketLength);
	packet_buffer[IP_HPF_LENGTH+1]						= LOW(in_header_info->PacketLength);
	packet_buffer[IP_HPF_IDENTIFIER]					= HIGH(l_ip_id);
	packet_buffer[IP_HPF_IDENTIFIER+1]				= LOW(l_ip_id);
	packet_buffer[IP_HFP_FRAGMENTATION]				= 0;
	packet_buffer[IP_HFP_FRAGMENTATION+1]			= 0;
	packet_buffer[IP_HPF_TTL]									= 0x80;
  packet_buffer[IP_HFP_PROTOCOL]            = in_header_info->Protocol;
	packet_buffer[IP_HPF_HEADER_CHECKSUM]			= 0;
	packet_buffer[IP_HPF_HEADER_CHECKSUM+1]		= 0;

  packet_buffer[IP_HFP_SOURCE_IP]           = g_netIPAddress.v[0];
  packet_buffer[IP_HFP_SOURCE_IP+1]         = g_netIPAddress.v[1];
  packet_buffer[IP_HFP_SOURCE_IP+2]         = g_netIPAddress.v[2];
  packet_buffer[IP_HFP_SOURCE_IP+3]         = g_netIPAddress.v[3];

  packet_buffer[IP_HFP_DESTINATION_IP]      = in_header_info->Address.v[0];
  packet_buffer[IP_HFP_DESTINATION_IP+1]    = in_header_info->Address.v[1];
  packet_buffer[IP_HFP_DESTINATION_IP+2]    = in_header_info->Address.v[2];
  packet_buffer[IP_HFP_DESTINATION_IP+3]    = in_header_info->Address.v[3];

  // Update CRC
  word_sum = 0;
  netUpdateTXPacketChecksum( in_header_info->HeaderLength, &word_sum );
  checksum = netIPCalculateChecksum( word_sum );

  // update checksum
  packet_buffer[IP_HPF_HEADER_CHECKSUM]			= HIGH(checksum);
  packet_buffer[IP_HPF_HEADER_CHECKSUM+1]		= LOW(checksum);
  
  // increment packet ID
  l_ip_id++;
}

///////////////////////////////////////////////////////////////////////////////
//! Updates IP packet length in packet header
//! \param IP header info containing the correct packet length
void netIPUpdateHeader( netIPHeaderInfo* in_header_info )
{
	dosByte* packet_buffer;
  dosDWord word_sum;
  dosWord checksum;
  
  // go to the buffer start
  netSetTXPacketPos( IP_HFP_VERSION_AND_LENGTH );
  packet_buffer = netGetTXPacketChunkBuffer();

	// set version and header length
	packet_buffer[IP_HPF_LENGTH]							= HIGH(in_header_info->PacketLength);
	packet_buffer[IP_HPF_LENGTH+1]						= LOW(in_header_info->PacketLength);

	// Update CRC
  word_sum = 0;
  netUpdateTXPacketChecksum( in_header_info->HeaderLength, &word_sum );
  checksum = netIPCalculateChecksum( word_sum );

  // update checksum
  packet_buffer[IP_HPF_HEADER_CHECKSUM]			= HIGH(checksum);
  packet_buffer[IP_HPF_HEADER_CHECKSUM+1]		= LOW(checksum);
}

///////////////////////////////////////////////////////////////////////////////
//! Processes IP packages (must be called periodically for proper operation)
void netIPProcess(void)
{
  dosDWord checksum;
  netIPHeaderInfo header_info;
  dosByte* header_buffer;
  IN_ADDR destination_address;
	
	// check if packet really available and IP packet
	if( netGetAvailablePacketType() != netPT_IP )
		return;
		
	// drop invalid packet length
  if( netGetRXPacketLength() < netIP_HEADER_LENGTH )
  {
  	netDeleteRXPacket();
    return;
  }

	// load IP header
	netOpenRXPacket();
  header_buffer = netGetRXPacketChunkBuffer();

  // check IP header

  // Make sure this is IPv4; we don't know how to handle anything else
  if((header_buffer[IP_HFP_VERSION_AND_LENGTH] & 0xF0) != 0x40u)
  {
  	netDeleteRXPacket();
    return;
  }

  // check header length
  header_info.HeaderLength = (header_buffer[IP_HFP_VERSION_AND_LENGTH] & 0x0F) << 2;
  if( header_info.HeaderLength < netIP_HEADER_LENGTH )
  {
  	netDeleteRXPacket();
    return;
  }

  // Make sure that this isn't an IP fragment
  if((header_buffer[IP_HFP_FRAGMENTATION] + (((dosWord)header_buffer[IP_HFP_FRAGMENTATION+1]) << 8)) & 0xFF3F)
  {
  	netDeleteRXPacket();
    return;
  }

  // Little-endianize the important IP field (Length)
  header_info.PacketLength = header_buffer[IP_HPF_LENGTH+1] + (((dosWord)header_buffer[IP_HPF_LENGTH]) << 8);

  // get protocol type
  header_info.Protocol = header_buffer[IP_HFP_PROTOCOL];

  // get source IP addresses
  header_info.Address.v[0] =  header_buffer[IP_HFP_SOURCE_IP];
  header_info.Address.v[1] =  header_buffer[IP_HFP_SOURCE_IP+1];
  header_info.Address.v[2] =  header_buffer[IP_HFP_SOURCE_IP+2];
  header_info.Address.v[3] =  header_buffer[IP_HFP_SOURCE_IP+3];

  // get destination IP addresses
  destination_address.v[0] =  header_buffer[IP_HFP_DESTINATION_IP];
  destination_address.v[1] =  header_buffer[IP_HFP_DESTINATION_IP+1];
  destination_address.v[2] =  header_buffer[IP_HFP_DESTINATION_IP+2];
  destination_address.v[3] =  header_buffer[IP_HFP_DESTINATION_IP+3];

  // calculate header checksum
  checksum = 0;
  netUpdateRXPacketChecksum( header_info.HeaderLength, &checksum);

  // check checksum
  if( netIPCalculateChecksum( checksum ) != 0 )
  {
  	netDeleteRXPacket();
    return;
  }

  // check destination IP address
  if( destination_address.Val != g_netIPAddress.Val &&
      destination_address.Val != 0xFFFFFFFF )
  {
  	netDeleteRXPacket();
    return;
  }

  // check protocol type            
  switch( header_info.Protocol )
  {
    // ICMP protocol
#ifdef netICMP_ENABLED
    case netIPDT_ICMP:
      netICMPProcess(&header_info);
      break;
#endif

    // TCP protocol
#ifdef netTCP_ENABLED
    case netIPDT_TCP:
      netTCPProcess(&header_info);
      break;
#endif
                  
    // UDP protocol
#ifdef netUDP_ENABLED
    case netIPDT_UDP:
      netUDPProcess(&header_info);
      break;
#endif

    // unknown packet
    default:
  	  netDeleteRXPacket();
      break;
   }
}

///////////////////////////////////////////////////////////////////////////////
//! Calculates IP checksum from sum of words
//! \param Sum of words
//! \return IP checksum
dosWord netIPCalculateChecksum( dosDWord in_checksum )
{
  // now calculate the sum over the bytes in the sum
  // until the result is only 16bit long
  while( (in_checksum>>16) != 0 )
  {
    in_checksum = (in_checksum & 0xFFFF) + (in_checksum >> 16);
  }
  
  // one's complement
  return ~((dosWord)in_checksum);
}